#include "vanila/virtualmachine.h"
#include "vanila/opcode.h"
#include "vanila/chunk.h"
#include "vanila/disassembler.h"
#include "vanila/scanner.h"
#include "vanila/compiler.h"
#include "vanila/environment.h"
#include "vanila/object.h"
#include "vanila/callframe.h"
#include "vanila/error.h"
#include "vanila/native.h"
#include "vanila/allocator.h"
#include "utils/hash.h"
#include "utils/format.h"
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>

namespace vanila
{
static Allocator* allocator = utils::Singleton<Allocator>::instance();

//! \brief virtual machine run!!
//! \param[in] finishFrameSize if the CallFrame size reach finishFrameSize, finish run
void VirtualMachine::run(size_t finishFrameSize)
{
    this->_currentFrame = &(this->_frames.back());
    
    for (;;)
    {   
        if (this->_showStack)
        {
            this->printStack();
            Disassembler::disassembleInstruction(this->_currentFrame->closure->function()->chunk(), 
                                                static_cast<size_t>(this->_currentFrame->ip - this->_currentFrame->closure->function()->chunk().data()));
        }

        // get current bytecode which ip point!
        OpCode instruction = this->readBytecode();
        switch (instruction)
        {
        case OpCode::CONSTANT:  this->push(this->readConstant()); break;
        case OpCode::NIL:       this->push(Value(ValueType::NIL)); break;
        case OpCode::TRUE:      this->push(Value(true)); break;
        case OpCode::FALSE:     this->push(Value(false)); break;
        case OpCode::POP:       this->pop(); break;
        case OpCode::GET_LOCAL:
        {
            uint8_t slot = this->readByte();
            this->push(this->_currentFrame->slots[slot]);
            break;
        }
        case OpCode::SET_LOCAL:
        {
            uint8_t slot = this->readByte();
            this->_currentFrame->slots[slot] = this->peek(0);
            break;
        }
        case OpCode::GET_GLOBAL:
        {
            // global varibale name
            ObjectString* name = this->readString();

            // find the name in globals
            auto iter = this->_globals.find(name);
            if (iter == this->_globals.end())
                this->runtimeError(utils::format("Undefined variable '%s'.", name->cstr()));

            // push it value into stack
            this->push(iter->second);
            break;
        }
        case OpCode::DEFINE_GLOBAL:
        {
            ObjectString* name = this->readString();
            this->_globals[name] = this->peek(0);
            this->pop();
            break;
        }
        case OpCode::SET_GLOBAL:
        {
            // global varibale name
            ObjectString* name = this->readString();

            // find the name in globals
            auto iter = this->_globals.find(name);
            if (iter == this->_globals.end())
                this->runtimeError(utils::format("Undefined variable '%s'.", name->cstr()));

            // assign it 
            iter->second = this->peek(0);
            break;
        }
        case OpCode::GET_UPVALUE:
        {
            uint8_t slot = this->readByte();
            this->push(*(this->_currentFrame->closure->upvalues()[slot]->location()));
            break;
        }
        case OpCode::SET_UPVALUE:
        {
            uint8_t slot = this->readByte();
            *(this->_currentFrame->closure->upvalues()[slot]->location()) = this->peek(0);
            break;
        }
        case OpCode::GET_PROPERTY:
        {
            // ip: [ GET_PROPERTY ] [ property name's index ]
            // stack: [ instance/class object ] 
            if (this->peek(0).isInstance() == true)
            {
                // get instance object and property's name
                ObjectInstance* instance = this->peek(0).asInstance();
                ObjectString* name = this->readString();

                // firstly, see property as a filed, find it in fields
                if (!instance->isNative())
                {
                    // find in instance's fields
                    auto iter = instance->fields().find(name);
                    if (iter != instance->fields().end())
                    {
                        // pop the instance, push the value
                        this->pop();
                        this->push(iter->second);
                        break;
                    }
                    // as a class's field
                    else
                    {
                        Value filed;
                        if (instance->klass()->findFiled(name, filed))
                        {
                            this->pop();
                            this->push(filed);
                            break;
                        }
                    }
                }

                // the property is not a filed, see it as a method
                // also this statement is not a method call(know from DotParser), so it a method bind
                this->tryBindMethod(instance->klass(), name);
                break;   
            }
            // Class.filed
            else if (this->peek(0).isClass() == true)
            {
                ObjectClass* klass = this->peek(0).asClass();
                ObjectString* name = this->readString();

                Value filed;
                if (klass->findFiled(name, filed))
                {   
                    this->pop();
                    this->push(filed);
                    break;
                }
                else
                {
                    std::cout << "3\n";
                    this->runtimeError(utils::format("no field %s in class %s", name->str().c_str(), klass->name()->str().c_str()));
                }
            }

            this->runtimeError("Only instances and class have properties.");
        }
        case OpCode::SET_PROPERTY:
        {
            if (this->peek(1).isInstance() == true)
            {
                // get instance and property name
                ObjectInstance* instance = this->peek(1).asInstance();
                ObjectString* name = this->readString();

                if (instance->isNative())
                    this->runtimeError("Can't set property for a native instance.");

                Value value = this->pop();
                instance->fields()[name] = value;
                
                this->pop();
                this->push(value);
                break;   
            }
            // Class.filed
            else if (this->peek(1).isClass() == true)
            {
                ObjectClass* klass = this->peek(1).asClass();
                ObjectString* name = this->readString();

                Value value = this->pop();
                klass->fields()[name] = value;

                this->pop();
                this->push(value);
                break;
            }

            this->runtimeError("Only instances and class have properties.");
        }
        case OpCode::GET_SUPER:
        {
            // stack: [ sub instance ][ supclass ]
            ObjectString* name = this->readString();
            ObjectClass* superclass = this->pop().asClass();

            // bind the sub instance to superclass's name method( bind to the method's 'this' )
            this->tryBindMethod(superclass, name);
            break;
        }
        case OpCode::EQUAL:
        case OpCode::GREATER:
        case OpCode::LESS:
        case OpCode::ADD:       
        case OpCode::SUBTRACT:
        case OpCode::MULTIPLY:
        case OpCode::DIVIDE:
        {
            this->push(this->binaryOperate(instruction));
            break;
        }
        case OpCode::NOT:       this->push(Value(this->isFalsey(this->pop()))); break;
        case OpCode::NEGATE:
        {
            const Value& value = this->peek(0);
            if (!value.isNumber())
                this->runtimeError("Operand must be a number.");
            if (value.isInteger())
                this->push(Value(-1 * this->pop().integer()));
            else
                this->push(Value(-1 * this->pop().decimal()));
            break;
        }
        case OpCode::JUMP:
        {
            uint16_t offset = this->readShort();
            this->_currentFrame->ip += offset;
            break;
        }
        case OpCode::JUMP_IF_FALSE:
        {
            uint16_t offset = this->readShort();
            if (this->isFalsey(this->peek(0)))
                this->_currentFrame->ip += offset;
            break;
        }
        case OpCode::LOOP:
        {
            uint16_t offset = this->readShort();
            this->_currentFrame->ip -= offset;
            break;
        }
        case OpCode::CALL:
        {
            // ip: [ GET_GLOBAL ] [ variable position ] [ the way to get arg1 ] [ the way to get arg2 ] ... [OpCode::CALL] [arg cnt]
            // stack: [ callable object ] [ arg1 ] [ arg2 ] ... [ argn ]
            uint8_t argCount = this->readByte();

            if ( !this->callValue(this->peek(argCount), argCount) )
                this->runtimeError("Can only call functions and classes.");
            
            // load the new frame
            this->_currentFrame = &(this->_frames.back());
            
            break;
        }
        case OpCode::INVOKE:
        {
            // ip: [ INVOKE ] [ method name ] [ argCount ]
            // stack: [ instance ] [ arg1 ] [ arg2 ] ...
            // see the stack as the function call stack
            // instance is the 'this' local variable!
            ObjectString* method = this->readString();
            uint8_t argCount = this->readByte();

            this->tryInvoke(method, argCount);
            this->_currentFrame = &(this->_frames.back());
            break;
        }
        case OpCode::SUPER_INVOKE:
        {
            // stack: [ sub instance ] [ arg1 ] [ arg2 ] [ .. ] [ superclass ]
            ObjectString* method = this->readString();
            int argCount = this->readByte();
            ObjectClass* superclass = this->pop().asClass();

            //  [ sub instance ] [ arg1 ] [ arg2 ] [ .. ] -> before call
            // if the menthod been called, the sub instance will be bound into method's 'this'
            this->invokeFromClass(superclass, method, argCount);

            this->_currentFrame = &(this->_frames.back());

            break;
        }
        case OpCode::CLOSURE:
        {
            ObjectFunction* function = this->readConstant().asFunction();
            ObjectClosure* closure = allocator->allocateClosure(function);
            this->push(Value(closure));

            // build upvalue array for closure
            for (uint32_t i = 0; i < closure->upvalues().size(); ++i)
            {
                uint8_t isLocal = this->readByte();
                uint8_t index = this->readByte();

                // if the closure is in local(now, the closure is not been called, so the upvalue is in current frame )
                if (isLocal)
                    closure->upvalues()[i] = this->captureUpvalue(this->_currentFrame->slots + index);
                // if the closure is not local, find in current closure's upvalue array
                else
                    closure->upvalues()[i] = this->_currentFrame->closure->upvalues()[index];
            }

            break;
        }
        case OpCode::CLOSE_UPVALUE:
        {
            // close upvalue: move variable to heap
            this->closeUpvalues(this->_stackTop - 1);
            this->pop();
            break;
        }
        case OpCode::RETURN:
        {
            // return value put on the stack top
            Value result = this->pop();
            this->closeUpvalues(this->_currentFrame->slots);
            this->_frames.pop_back();
            
            // the function's stack is where the sript call it!
            this->_stackTop = this->_currentFrame->slots;
            this->push(result);

            if (!this->_frames.empty())
                this->_currentFrame = &(this->_frames.back());

            if (this->_frames.size() == finishFrameSize)
                return;

            break;
        }
        case OpCode::CLASS:
        {
            // ip: [ OpCode::CLASS ][ name index ]
            ObjectString* name = this->readString();
            this->push(Value(allocator->allocateClass(name)));
            break;    
        }
        case OpCode::INHERIT:
        {
            // stack: [ super class ][ sub class ]
            if (this->peek(1).isClass() == false)
                this->runtimeError("Superclass must be a class.");
                
            ObjectClass* superClass = this->peek(1).asClass();
            ObjectClass* subClass = this->peek(0).asClass();

            // copy all superClass's method to subClass
            // if the subclass define the same name function, superclass's method will be override
            for (const std::pair<ObjectString*, Value>& kv : superClass->methods())
                subClass->methods()[kv.first] = kv.second;

            // pop the class object 
            this->pop();
            break;
        }
        case OpCode::METHOD:
        {
            this->defineMethod(this->readString());
            break;
        }
        case OpCode::FIELD:
        {
            // stack belike: [ class ][ field ]
            ObjectString* name = this->readString();
            Value field = this->peek(0);
            ObjectClass* klass = this->peek(1).asClass();
            klass->addField(name, field);

            // pop the [ field ]
            this->pop();
            break;
        }
        case OpCode::LIST:
        {
            // stack [ e1 ] [ e1 ] [ ... ] [ en ]
            uint16_t elementCount = this->readShort();
            this->_stackTop -= elementCount;

            ObjectList* list = allocator->allocateList(this->_stackTop, elementCount);
            ObjectInstance* instance = allocator->allocateInstance(ObjectClass::list);

            instance->setObject(list);
            this->push( Value{instance} );
            break;
        }
        case OpCode::DICT:
        {
            // stack [ key1 ] [ value1 ] [ key2 ] [ value2 ] [ .. ] [ keyn ] [ valuen ]
            uint16_t pairSize = this->readShort();
            this->_stackTop -= (pairSize << 1);

            ObjectDictionary* dict = allocator->allocateDictionary(this->_stackTop, pairSize);
            ObjectInstance* instance = allocator->allocateInstance(ObjectClass::dict);

            instance->setObject(dict);
            this->push( Value{instance} );
            break;
        }
        case OpCode::SET:
        {
            // stack [ v1 ] [ v2 ] [ v2 ]...
            uint16_t elementSize = this->readShort();
            this->_stackTop -= (elementSize);

            ObjectSet* set = allocator->allocateSet(this->_stackTop, elementSize);
            ObjectInstance* instance = allocator->allocateInstance(ObjectClass::set);
            
            instance->setObject(set);
            this->push( Value{instance} );
            break;
        }
        case OpCode::GET_INDEX:
        {
            // stack [ iterable instance ] [ index ]
            Value index = this->pop();
            Value iterable = this->pop();

            if (iterable.isInstance() && iterable.asInstance()->isNative())
            {
                ObjectInstance* instance = iterable.asInstance();
                if (instance->object()->isList())
                {
                    if (!index.isInteger())
                        this->runtimeError("Only number can be a index!");

                    ObjectList* list = instance->object()->asList();
                    int64_t i = index.integer();
                    Value value{};
                    if (list->get(i, value))
                        this->push(value);
                    else
                        this->runtimeError("Index out of range");
                    break;
                }
                else if (instance->object()->isDictionary())
                {
                    ObjectDictionary* dict = instance->object()->asDictionary();
                    Value value{};
                    if (dict->get(index, value))
                        this->push(value);
                    else
                        this->runtimeError("Key error");
                    break;
                }
            }
            else
                this->runtimeError("Expected a iterable");
            break;
        }
        case OpCode::SET_INDEX:
        {
            // stack [ iterable instance ] [ index ] [ new value ]
            Value value = this->pop();
            Value index = this->pop();
            Value iterable = this->pop();

            if (iterable.isInstance() && iterable.asInstance()->isNative())
            {
                ObjectInstance* instance = iterable.asInstance();
                if (instance->object()->isList())
                {
                    if (!index.isInteger())
                        this->runtimeError("Only number can be a index!");
                    size_t i = index.integer();
                    (*(instance->object()->asList()))[i] = value;
                    this->push( value );
                    break;
                }
                else if (instance->object()->isDictionary())
                {
                    ObjectDictionary* dict = instance->object()->asDictionary();
                    dict->set(index, value);
                }
                this->push( value );
                break;
            }
            else
                this->runtimeError("Expected a iterable");

            break;
        }
        }
    }
}

}